/*
 * A7106.c
 *
 *  Created on: 2023年11月1日
 *      Author: fangjian
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/param.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "driver/spi_master.h"
#include "driver/gpio.h"

#include "sdkconfig.h"
#include "esp_log.h"

#include "hal_a7106.h"

// https://github.com/rootlocal/stm32-A7106/blob/master/src/A7106_spi.c#L84
// 濠江电动床控制盒射频模块破解

#define TAG "a7106"

#define A7106_DEBUG_ENABLE      1

#if A7106_DEBUG_ENABLE
#define A7106_LOGI(format, ...)    ESP_LOGI(TAG, format, ##__VA_ARGS__)
#define A7106_LOGW(format, ...)    ESP_LOGW(TAG, format, ##__VA_ARGS__)
#define A7106_LOGE(format, ...)    ESP_LOGE(TAG, format, ##__VA_ARGS__)
#else
#define A7106_LOGI(format, ...)
#define A7106_LOGW(format, ...)
#define A7106_LOGE(format, ...)
#endif
 
#define PIN_NUM_MISO -1
#define PIN_NUM_MOSI 6
#define PIN_NUM_CLK  7
#define PIN_NUM_CS   15

static spi_device_handle_t spi; ///< SPI device handle
 
// Workaround: The driver depends on some data in the flash and cannot be placed to DRAM easily for
// now. Using the version in LL instead.
#define gpio_set_level gpio_set_level_patch
#include "hal/gpio_ll.h"
static inline esp_err_t gpio_set_level_patch(gpio_num_t gpio_num, uint32_t level)
{
    gpio_ll_set_level(&GPIO, gpio_num, level);
    return ESP_OK;
}
 
static esp_err_t spi_write(spi_device_handle_t spi, uint8_t *data, uint8_t len)
{
    esp_err_t ret;
    spi_transaction_t t;
    memset(&t, 0, sizeof(t)); // Zero out the transaction
    t.length    = len * 8;                      // Len is in bytes, transaction length is in bits.
    t.tx_buffer = data;                         // Data
    ret = spi_device_polling_transmit(spi, &t); // Transmit!
    return ret;
}

static esp_err_t spi_write_cs_keep(spi_device_handle_t spi, uint8_t *data, uint8_t len)
{
    esp_err_t ret;
    spi_transaction_t t;
    memset(&t, 0, sizeof(t));                     // Zero out the transaction
    t.length    = len * 8;                        // Len is in bytes, transaction length is in bits.
    t.tx_buffer = data;                           // Data
    t.flags     = SPI_TRANS_CS_KEEP_ACTIVE;       // Keep CS active after data transfer
    ret = spi_device_polling_transmit(spi, &t);   // Transmit!
    return ret;
}

static esp_err_t spi_read(spi_device_handle_t spi, uint8_t *data, uint8_t len)
{
    esp_err_t ret;
    spi_transaction_t t;
    memset(&t, 0, sizeof(t));  
    t.length    = len * 8;                          
    t.rxlength  = t.length;
    t.rx_buffer = data;                         
    ret = spi_device_polling_transmit(spi, &t); 
    return ret;
}

// 读写
static esp_err_t spi_read_write(uint8_t *txbuff, uint8_t txlen, uint8_t *rxbuff, uint8_t rxlen)
{
    // When using SPI_TRANS_CS_KEEP_ACTIVE, bus must be locked/acquired
    spi_device_acquire_bus(spi, portMAX_DELAY);
 
    esp_err_t ret;
    ret = spi_write_cs_keep(spi, txbuff, txlen);  // write...
    ret = spi_read(spi, rxbuff, rxlen);    // read...
 
    spi_device_release_bus(spi);  // Release bus

    return ret;
}

static void spi_init(void)
{
    A7106_LOGI("Initializing spi bus...");

    spi_bus_config_t buscfg = {
        .miso_io_num = PIN_NUM_MISO,
        .mosi_io_num = PIN_NUM_MOSI,
        .sclk_io_num = PIN_NUM_CLK,
        .quadwp_io_num = -1,
        .quadhd_io_num = -1,
        .max_transfer_sz = 16,
        .flags = SPICOMMON_BUSFLAG_MASTER,
    };
    // Initialize the SPI bus
    ESP_ERROR_CHECK(spi_bus_initialize(SPI3_HOST, &buscfg, SPI_DMA_CH_AUTO));

    spi_device_interface_config_t devcfg = {
        .clock_speed_hz = SPI_MASTER_FREQ_10M, 
        .spics_io_num = PIN_NUM_CS,
        .cs_ena_pretrans = 4,
        .cs_ena_posttrans = 4,
        .queue_size = 8,
        .flags = SPI_DEVICE_3WIRE | SPI_DEVICE_HALFDUPLEX,  // 3线半双工
        .input_delay_ns = 50, // output the data half a SPI clock behind.
    };
    // Attach the EEPROM to the SPI bus
    ESP_ERROR_CHECK(spi_bus_add_device(SPI3_HOST, &devcfg, &spi));
}
//===========================================================================================================
//===========================================================================================================
#if 1
  
void a7106_write_register(uint8_t reg, uint8_t val)
{
    uint8_t write[2];
    write[0] = reg;
    write[1] = val;
    spi_write(spi, write, 2);
}
 
uint8_t a7106_read_register(uint8_t reg)
{
    uint8_t val;
    reg |= 0x40;
    spi_read_write(&reg, 1, &val, 1);
    return val;
}

void a7106_write_strobe(a7106_strobe_t strobe)
{
    uint8_t val = (strobe | 0x80) & 0xF0;
    spi_write(spi, &val, 1);
}

void a7106_set_channel(uint8_t channel)
{
    a7106_write_register(A7105_0F_PLL_I, channel);
}

// 发送流程： A7105_STROBE_RX -> A7105_STROBE_STANDBY -> A7105_STROBE_TX
a7106_strobe_t a7106_write_data(const uint8_t *data, uint8_t len)
{
    // esp_log_buffer_hex("rfTX", data, len);
    uint8_t write[8];
    write[0] = A7105_STROBE_RST_WRPTR;
    write[1] = A7105_05_FIFO_DATA;
    memcpy(write + 2, data, len);
    spi_write(spi, write, len + 2);
    a7106_write_strobe(A7105_STROBE_TX);
    return A7105_STROBE_TX;
}

uint8_t a7106_read_data(uint8_t *data, uint8_t len)
{
    uint8_t write[2];
    write[0] = A7105_STROBE_RST_RDPTR;
    write[1] = A7105_05_FIFO_DATA | 0x40;
    spi_read_write(write, 2, data, len);
    a7106_write_strobe(A7105_STROBE_RX);
    return len;
}

void a7106_write_id(a7106_id_t id)
{
    uint8_t write[5];
    write[0] = A7105_06_ID_DATA;
    memcpy(write + 1, id.u8, 4);
    spi_write(spi, write, 5);
}

void a7106_read_id(a7106_id_t *id)
{
    uint8_t reg = A7105_06_ID_DATA | 0x40;
    spi_read_write(&reg, 1, id->u8, 4);
}

void a7106_set_tx_power(tx_power_t power)
{
    uint8_t PAC_reg, TBG_reg;
    switch (power) {
    case 0:
        PAC_reg = 0;
        TBG_reg = 0;
        break;
    case 1:
        PAC_reg = 0;
        TBG_reg = 1;
        break;
    case 2:
        PAC_reg = 0;
        TBG_reg = 2;
        break;
    case 3:
        PAC_reg = 0;
        TBG_reg = 4;
        break;
    case 4:
        PAC_reg = 1;
        TBG_reg = 5;
        break;
    case 5:
        PAC_reg = 2;
        TBG_reg = 7;
        break;
    case 6:
    case 7:
        PAC_reg = 3;
        TBG_reg = 7;
        break;
    default:
        PAC_reg = 0;
        TBG_reg = 0;
        break;
    }
    a7106_write_register(A7105_28_TX_TEST, (PAC_reg << 3) | TBG_reg);
}

 
uint8_t a7106_calib(void)
{
    uint8_t retVal = 0;
    uint8_t calibReg;

    /// Assume MFBS == 0 after reset
    a7106_write_strobe(A7105_STROBE_PLL);

    /// Start filter bank (FBC, bit 0), VCO current (VCC, bit 2) and VCO
    /// bank (VBC, bit 1) calibrations all at the same time.
    /// Leave VTH and VTL at recommended values.
    a7106_write_register(A7105_02_CALC, 0x07);

    /// We could poll the end of the calibration and use a timer to determine
    /// if it takes too long. That's not easily portable, so we waste some
    /// time instead.
    vTaskDelay(10);

    calibReg = a7106_read_register(A7105_02_CALC);

    A7106_LOGI("a7106_calib.START = 0x%02x", calibReg);

    /// Check calibration timeouts
    if (A7105_TEST_BIT(calibReg, 0)) {
        retVal |= 0x10; ///< IF filter bank calibration took too long
    }

    if (A7105_TEST_BIT(calibReg, 1)) {
        retVal |= 0x01; ///< VCO bank calibration took too long
    }

    if (A7105_TEST_BIT(calibReg, 2)) {
        retVal |= 0x04; ///< VCO current calibration took too long
    }

    /// Check calibration success
    /// FBCF bit: Indicates calib failure
    if (A7105_TEST_BIT(a7106_read_register(A7105_22_IF_CALIB_I), 4)) {
        retVal |= 0x20; ///< IF calib not successful
    }

    /// VBCF bit: Indicates calib failure
    if (A7105_TEST_BIT(a7106_read_register(A7105_25_VCO_SBCAL_I), 3)) {
        retVal |= 0x02; ///< VCO bank calibration not successful
    }

    /// FVCC bit: Indicates calib failure
    if (A7105_TEST_BIT(a7106_read_register(A7105_24_VCO_CURCAL), 4)) {
        retVal |= 0x08; ///< VCO current calib not successful
    }

    A7106_LOGI("a7106_calib.END = 0x%02x", retVal);

    a7106_write_strobe(A7105_STROBE_STANDBY);

    return retVal;
}
 

uint8_t a7106_reset(void)
{
    uint8_t result;
    a7106_write_register(A7105_00_MODE, 0x00); ///< Write to this register by 0x00 to issue reset command, then it is auto clear
    vTaskDelay(100);
    result = a7106_read_register(A7105_10_PLL_II);
    A7106_LOGI("A7105_10_PLL_II = 0x%x", result);
    a7106_write_strobe(A7105_STROBE_STANDBY);
    return result;
}


  
const uint8_t config_table[] = {
    0x00,// reset register
    0x42,//mode register:FIFO mode
    0x00,//calibration register
    RF_FIFO_LEN - 1, //FIFO1 register:packet length
    0x00,//FIFO2 register,,4/60byte(TX/RX)FIFO
    0x00,//FIFO register,,R/W data register
    0x00,//ID DATA register,not use on config
    0x00,//RCOSC1 register
    0x00,//RCOSC2 register
    0x00,//RCOSC3 register
    0x00,//CKO register
    0x0d,//GPIO1 register,,GPIO1 PIN Enable Output  RX: 0b00 0011 01 = [0x0d] >> Preamble OK output
    0x21,//GPIO2 register
    0x05,//CLOCK register:Crystal oscillator enable bit
    0x13,//DATA RATE register,,,500k
    0x63,//PLL1 register
    0x9e,//PLL2 register
    0x4b,//PLL3 register
    0x00,//PLL4 register
    0x02,//PLL5 register
    0x16,//TX1 register
    0x2b,//TX2 register
    0x12,//DELAY1 register
    0x00,//DELAY2 register
    0x66,//RX register,Demodulator filter bandwidth = 1M,Demodulator gain select * 1,BPF:bandwidth 500khz
    0x80,//RX GAIN1 register,手动校验VGA,PGA gain select 12db,mixer gain select 24db,LNA gain select 24db.
    0x80,//RX GAIN2 register,VGA calibrate upper limit target
    0x00,//RX GAIN3 register,VGA calibrate lower limit target
    0x0a,//RX GAIN4 register,在设定的目标内持续调整,直到收到ID CODE为止，Mixer high current 1.2ma,LNA current select 1.0ma
    0x32,//RSSI register
    0xc3,//ADC register
    0x0f,//CODE1 register;ID length 4 bytes, preamble length 4 bytes
    0x17,//CODE2 register
    0x00,//CODE3 register
    0x00,//IF CALI1 register
    0x00,//IF CALI2 register
    0x00,//VCO_C_CALI register
    0x00,//VCO CALI1 register
    0x3b,//VCO CALI2 register
    0x00,//BATTERY register
    0x17,//TX TEST register! <TX_POWER>
    0x27,//RX DEM1 register
    0x80,//RX DEM2 register
    0x03,//CPC register
    0x01,//CRYSTAL register
    0x45,//PLL TEST register
    0x18,//VCO TEST1 register
    0x00,//VCO TSET2 register
    0x01,//IFAT register
    0x0f,//RSCALE register
    0x00//FILTERTEST register
};
 
 
void a7106_init(void)
{
    a7106_reset();

    // 0x00 mode register, for reset
    // 0x05 fifo data register
    // 0x06 id code register
    // 0x23 IF calibration II, only read
    // 0x32 filter test register
    uint8_t i = 0;
    for (i = 0x01; i <= 0x04; i++) {
        a7106_write_register(i, config_table[i]);
    }

    for (i = 0x07; i <= 0x0e; i++) {
        a7106_write_register(i, config_table[i]);
    }

    for (i = 0x0f; i <= 0x22; i++) {
        a7106_write_register(i, config_table[i]);
    }

    for (i = 0x24; i <= 0x31; i++) {
        a7106_write_register(i, config_table[i]);
    }
 
    a7106_calib();

    a7106_set_tx_power(TXPOWER_150mW);
}

#endif
 
void hal_a7106_init(void)
{
    spi_init();

    a7106_init();
 
 #if 0  // test...
    a7106_id_t id;
    id.u32 = 0x2d692d69; // A7105_ID_CFIG; // 0x2d692d69; 
    A7106_LOGI("a7106_write_id = 0x%lx", id.u32);
    a7106_write_id(id);
    a7106_read_id(&id);
    A7106_LOGI("a7106_read_id = 0x%lx", id.u32);
 
    uint8_t channel = A7105_CH_CFIG;
    if (id.u32 != A7105_ID_CFIG) {
        channel = id.u8[1];
    }  
    a7106_set_channel(channel);
 
    a7106_write_strobe(A7105_STROBE_RX);

    while (1) {
  
        // 发送测试！
        a7106_set_channel(channel + 1);
        a7106_write_strobe(A7105_STROBE_STANDBY);
        vTaskDelay(10);
        memset(id.u8, 0x55, 4);   // 头部下降按钮
        a7106_write_data(id.u8, 4);
        esp_log_buffer_hex("TX", id.u8, RF_FIFO_LEN);
 
        vTaskDelay(100);  // 等待发送完延时切换接收通道！
        a7106_set_channel(channel);
        a7106_write_strobe(A7105_STROBE_RX);
 
        #if 0
        while (1) {
            a7106_read_data(id.u8, RF_FIFO_LEN);  // 读数据
            if (id.u8[0] != 0) {
                esp_log_buffer_hex("RX", id.u8, RF_FIFO_LEN);
                ESP_LOGI("A7105", "a7106_read_id = 0x%lx", id.u32);
                break;
            }
            vTaskDelay(100);
        }
        #endif
 
        vTaskDelay(5000);
    }
#endif
}

// 小米IOT开发者平台 -> 功能定义！
//0: 暂停
//1: 休闲模式
//2: 深睡模式
//3: TV模式
//4: 阅读模式
//5: 零重力
//6: 防打鼾
const uint8_t RF_MODE_TABLE[][4] = {
    RF_MOTOR_NULL,
    RF_MOTOR_MODE_CASUAL,
    RF_MOTOR_MODE_SLEEP,
    RF_MOTOR_MODE_TV,
    RF_MOTOR_MODE_READ,
    RF_MOTOR_MODE_ZERO,
    RF_MOTOR_MODE_SNORE,
};

//0: 无
//1: 背升
//2: 背降
//3: 腿升
//4: 腿降
//5: 同升
//6: 同降
const uint8_t RF_ANGLE_TABLE[][4] = {
    RF_MOTOR_NULL,
    RF_MOTOR_BACK_UP,
    RF_MOTOR_BACK_DOWN,
    RF_MOTOR_LEG_UP,
    RF_MOTOR_LEG_DOWN,
    RF_MOTOR_ALL_UP,
    RF_MOTOR_ALL_DOWN,
};

extern void rf_motor_write(const uint8_t *data, uint8_t reissue);

// 设置电动床-模式
void motor_set_mode(uint8_t index)
{
    rf_motor_write(RF_MODE_TABLE[index], 1);
}

// 设置电动床-角度
void motor_set_angle(uint8_t index)
{
    rf_motor_write(RF_ANGLE_TABLE[index], 3);
}

